/*
 * Copyright 2011 Angel Sanadinov
 *
 * This file is part of VBox WMI.
 *
 * VBox WMI is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * VBox WMI is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with VBox WMI.  If not, see <http://www.gnu.org/licenses/>.
 */

package Services;

import Beans.Requests.LoginRequest;
import Beans.Requests.LogoutRequest;
import Beans.Responses.LoginResponse;
import Managers.DatabaseManager;
import Managers.LogsManager;
import Managers.SessionsManager;
import Utilities.Constants;
import Utilities.ExceptionMessages;
import Utilities.ApplicationException;
import Utilities.ParameterNames;
import Utilities.Functions;
import java.io.IOException;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * This servlet represents the application's Login Service. <br><br>
 *
 * This service takes care of the user login/logout.
 *
 * @author Angel Sanadinov
 */
public class LoginService extends HttpServlet
{
    private LogsManager logsManager;         //logs manager
    private DatabaseManager databaseManager; //database manager
    private SessionsManager sessionsManager; //sessions manager
    private String passwordHashingAlgorithm; //algorithm for hashing user passwords
    private String systemCharsetName;        //default system charset

    /**
     * Initializes the service and retrieves all necessary managers.
     *
     * @throws ServletException if an exception occurs that interrupts the servlet's
     *                          normal operation
     *
     * @see HttpServlet#init()
     */
    @Override
    public void init() throws ServletException
    {
        ServletContext context = getServletContext();
        logsManager = (LogsManager)context.getAttribute(Constants.CONTEXT_LOGS_MANAGER);
        databaseManager = (DatabaseManager)context.getAttribute(Constants.CONTEXT_DATABASE_MANAGER);
        sessionsManager = (SessionsManager)context.getAttribute(Constants.CONTEXT_SESSIONS_MANAGER);
        passwordHashingAlgorithm = context.getInitParameter(ParameterNames.PASSWORD_HASHING_ALGORITHM);
        systemCharsetName = context.getInitParameter(ParameterNames.DEFAULT_CHARSET);
    }

    /**
     * Shuts down the service and cleans up any object references.
     *
     * @see HttpServlet#destroy() 
     */
    @Override
    public void destroy()
    {
        logsManager = null;
        databaseManager = null;
        sessionsManager = null;
    }

    /**
     * Processes a <code>GET</code> request from a client.
     *
     * @param request an HttpServletRequest object that contains the request the
     *                client has made of the servlet
     * @param response an HttpServletResponse object that contains the response
     *                 the servlet sends to the client
     * @throws ServletException if the request for the GET could not be handled
     * @throws IOException if an input or output error is detected when the servlet
     *         handles the GET request
     * 
     * @see HttpServlet#doGet(HttpServletRequest, HttpServletResponse)
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException
    {
        processRequest(request, response);
    } 

    /**
     * Processes a <code>POST</code> request from a client.
     *
     * @param request an HttpServletRequest object that contains the request the
     *                client has made of the servlet
     * @param response an HttpServletResponse object that contains the response
     *                 the servlet sends to the client
     * @throws ServletException if the request for the POST could not be handled
     * @throws IOException if an input or output error is detected when the servlet
     *         handles the POST request
     *
     * @see HttpServlet#doPost(HttpServletRequest, HttpServletResponse)
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException
    {
        processRequest(request, response);
    }

    /**
     * Processes all <code>GET</code> and <code>POST</code> requests and carries out
     * operations based on requested actions.
     *
     * @param request an HttpServletRequest object that contains the request the
     *                client has made of the servlet
     * @param response an HttpServletResponse object that contains the response
     *                 the servlet sends to the client
     * @throws ServletException if the request could not be handled
     * @throws IOException if an input or output error is detected when the service
     *         handles the request
     */
    private void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException
    {
        //retrieves both login and logout requests
        LoginRequest loginRequest = (LoginRequest)request.getAttribute(ParameterNames.REQUEST_LOGIN);
        LogoutRequest logoutRequest = (LogoutRequest)request.getAttribute(ParameterNames.REQUEST_LOGOUT);

        //checks if a login request was made...
        if(loginRequest != null && loginRequest.isValid())
        {
            logsManager.logSystemEvent(System.currentTimeMillis(), Constants.LOG_SEVERITY_DEBUG,
                                       "Request: " + loginRequest.toString());

            int userId = Utilities.Constants.INVALID_USER_ID;

            //attempts to validate the credentials of the user
            try
            {
                userId = databaseManager.verifyUserCredentials(loginRequest.getUsername(),
                                                               Functions.hashPassword(loginRequest.getPassword(), 
                                                                                      passwordHashingAlgorithm,
                                                                                      systemCharsetName));
            }
            catch(Exception e)
            {
                logsManager.logSystemEvent(System.currentTimeMillis(), Constants.LOG_SEVERITY_ERROR, e.getMessage());
                processResponse(request, response, new ApplicationException(ExceptionMessages.APP_E_CREDENTIALS_VERIFICATION_FAILED));
            }

            //if the user was properly validated, a valid user id should be available
            if(Functions.isUserIdValid(userId))
            {
                //attempts to register the user with the sessions manager
                if(sessionsManager.loginUser(userId, loginRequest.getUserSession(), loginRequest.getUserRemoteAddress()))
                    response.sendRedirect("/VBoxWMI/index.jsp");//redirect to main page
                else
                    processResponse(request, response, new ApplicationException(ExceptionMessages.APP_E_SESSION_SETUP_FAILED));
            }
            else
                processResponse(request, response, new ApplicationException(ExceptionMessages.APP_E_INVALID_CREDENTIALS));
        }
        //checks if a logout request was made
        else if(logoutRequest != null && logoutRequest.isValid())
        {
            logsManager.logSystemEvent(System.currentTimeMillis(), Constants.LOG_SEVERITY_DEBUG,
                                       "Request: " + logoutRequest.toString());
            //unregisters the user from the sessions manager
            sessionsManager.logoutUser(logoutRequest.getRequestorId(), logoutRequest.getUserSession());
            response.sendRedirect("/VBoxWMI/index.jsp");//redirect to main page
        }
        else //...an invalid request was made
            processResponse(request, response, new ApplicationException(ExceptionMessages.APP_E_INVALID_REQUEST));
    }

    /**
     * Processes all responses going out of the service.
     *
     * @param request an HttpServletRequest object that contains the request the
     *                client has made of the servlet
     * @param response an HttpServletResponse object that contains the response
     *                 the servlet sends to the client
     * @param exception an exception that occurred during request processing
     * @throws ServletException if the processing of the response could not be handled
     * @throws IOException if an input or output error is detected when the response
     *                     is handled
     */
    private void processResponse(HttpServletRequest request, HttpServletResponse response, ApplicationException exception)
            throws ServletException, IOException
    {
        logsManager.logSystemEvent(System.currentTimeMillis(), Constants.LOG_SEVERITY_DEBUG,
                                       "Request execution failed: " + exception.toString());
        request.setAttribute(ParameterNames.REQUEST_LOGIN, null);
        request.setAttribute(ParameterNames.RESPONSE_LOGIN, new LoginResponse(false, exception));
        request.getRequestDispatcher("login.jsp").forward(request, response);
    }
}
